import java.io.*;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
import java.util.Vector;


@SuppressWarnings("serial")
public class Automata implements Serializable {

	private Vector<State> states;
	private State initialState;
	private State finalState;
	private AutomataVisualizer visualizer;

	public Automata()
	{

		initialState=null;
		finalState=null;
	}


	public Automata(Transition trans)
	{
		initialState=new State();
		finalState=new State();
		trans.setDestinationState(finalState);
		initialState.addTransition(trans);

	}
	public Automata(String value)
	{
		initialState=new State();
		finalState=new State();
		initialState.addTransition(new Transition(finalState,value));

	}

	public void createVisualization(){
		visualizer = new AutomataVisualizer();
		initialState.visited = true;

		Queue<State> states = new LinkedList<State>();
		states.add(initialState);
		initialState.vertexNo = visualizer.addVertex();

		while(!states.isEmpty()){
			State temp = states.poll();
			for(int i = 0; i < temp.getTransitions().size(); i++){
				Transition trans = temp.getTransitions().get(i);
				if(!trans.visited){
					trans.visited = true;
					State dest = trans.getDestinationState();

					if(!dest.visited){
						states.add(dest);
						dest.vertexNo = visualizer.addVertex();
						dest.visited = true;
					}
					visualizer.addEdge(trans.getValue(), temp.vertexNo, dest.vertexNo);
				}
			}
		}

		visualizer.view(1024, 700);

	}

	public Vector<State> getStates() {
		return states;
	}
	public void setStates(Vector<State> states) {
		this.states = states;
	}
	public State getFinalState() {
		return finalState;
	}
	public void setFinalState(State finalState) {
		this.finalState = finalState;
	}
	public State getInitialState() {
		return initialState;
	}
	public void setInitialState(State initialState) {
		this.initialState = initialState;
	}
	public Object clone(Serializable obj) {
		ObjectOutputStream out = null;
		ObjectInputStream in = null;

		try {
			ByteArrayOutputStream bout = new ByteArrayOutputStream();
			out = new ObjectOutputStream(bout);

			out.writeObject(obj);
			out.close();

			ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
			in = new ObjectInputStream(bin);			
			Object copy = in.readObject();

			in.close();

			return copy;
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			try {
				if(out != null) {
					out.close();
				}

				if(in != null) {
					in.close();
				}
			} catch (IOException ignore) {}
		}

		return null;
	}
	public void concatenate(Automata automata)
	{
		if(!automata.initialState.allTransitionsE()){
			this.finalState.addTransition(new Transition(automata.initialState));
			State newInitialState = new State();
			State newFinalState = new State();
	
			newInitialState.addTransition(new Transition(this.initialState));
			automata.getFinalState().addTransition(new Transition(newFinalState));
	
			setInitialState(newInitialState);
			setFinalState(newFinalState);
		}
		else
		{
			this.getFinalState().addTransition(new Transition(automata.getInitialState()));
			this.finalState = automata.getFinalState();
		}
	}
	public void disjoint(Automata automata)
	{
		

		if(!automata.initialState.allTransitionsE()){
			State newInitialState = new State();
			State newFinalState = new State();
			
			//mudar o estado final dos dois automatos
			this.getFinalState().addTransition(new Transition(newFinalState));
			automata.getFinalState().addTransition(new Transition(newFinalState));
			
			//mudar o estado inicial do automato 
			newInitialState.addTransition(new Transition(this.getInitialState()));
			newInitialState.addTransition(new Transition(automata.getInitialState()));
			this.setInitialState(newInitialState);this.setFinalState(newFinalState);
		}
		else{
			automata.initialState.addTransition(new Transition(this.getInitialState()));
			this.getFinalState().addTransition(new Transition(automata.getFinalState()));
			
			this.initialState = automata.getInitialState();
			this.finalState = automata.getFinalState();
			
		}
		

	}
	public void star()//asterisco -*
	{
		State newInitialState = new State();
		State newFinalState = new State();

		//adicionar transicao para permitir o ciclo
		this.getFinalState().addTransition(new Transition(initialState));

		//adicionar transicao do novo estado inicial para o estado inicial 
		newInitialState.addTransition(new Transition(this.getInitialState()));

		//adicionar transicao do estado final para o novo estado final
		this.getFinalState().addTransition(new Transition(newFinalState));

		//mudar os estados inicial e final
		this.setInitialState(newInitialState);
		this.setFinalState(newFinalState);

		//adicionar transicao do estado inicial para o final, devido a possibilidade de e nao ocorrer nenhuma vez 
		this.getInitialState().addTransition(new Transition(this.getFinalState()));

	}
	public void optional()//interrogacao - ?
	{
		State newInitialState = new State();
		State newFinalState = new State();


		//adicionar transicao do novo estado inicial para o estado inicial 
		newInitialState.addTransition(new Transition(this.getInitialState()));

		//adicionar transicao do estado final para o novo estado final
		this.getFinalState().addTransition(new Transition(newFinalState));

		//mudar os estados inicial e final
		this.setInitialState(newInitialState);
		this.setFinalState(newFinalState);

		//adicionar transicao do estado inicial para o final, devido a possibilidade de e nao ocorrer nenhuma vez 
		this.getInitialState().addTransition(new Transition(this.getFinalState()));

	}
	public void plus()//sinal +
	{
		State newInitialState = new State();
		State newFinalState = new State();

		//adicionar transicao para permitir o ciclo
		this.getFinalState().addTransition(new Transition(initialState));

		//adicionar transicao do novo estado inicial para o estado inicial 
		newInitialState.addTransition(new Transition(this.getInitialState()));

		//adicionar transicao do estado final para o novo estado final
		this.getFinalState().addTransition(new Transition(newFinalState));

		//mudar os estados inicial e final
		this.setInitialState(newInitialState);
		this.setFinalState(newFinalState);

	}
	public void quantifier(int numOccurs)//{numOccurs}
	{
		Automata tmp = (Automata)clone(this);
		for(int i = 0; i<numOccurs;i++)
		{
			if(i==0)
			{
				State newInitialState = new State();

				//adicionar transicao do novo estado inicial para o estado inicial 
				newInitialState.addTransition(new Transition(this.getInitialState()));

				//mudar o estado inicial
				this.setInitialState(newInitialState);
			}
			else
			{
				Automata at = (Automata)clone(tmp);

				//transicao do estado final para o estado inicial do clone
				this.getFinalState().addTransition(new Transition(at.getInitialState()));

				//mudar o estado final para o estado final do clone	
				this.setFinalState(at.getFinalState());

			}
		}
		State newFinalState = new State();

		//transicao do estado final para o novo estado final
		this.getFinalState().addTransition(new Transition(newFinalState));

		//mudar o estado final 
		this.setFinalState(newFinalState);

	}
	public void quantifierMin(int numMin)//{numMin,}
	{
		State stateTmp=null;
		Automata tmp = (Automata)clone(this);
		for(int i = 0; i<numMin;i++)
		{
			if(i==0)
			{
				State newInitialState = new State();

				//adicionar transicao do novo estado inicial para o estado inicial 
				newInitialState.addTransition(new Transition(this.getInitialState()));

				//mudar o estado inicial
				this.setInitialState(newInitialState);
			}
			else
			{
				Automata at = (Automata)clone(tmp);

				if(i==numMin-1)
					stateTmp=at.getInitialState();//guardar o no para o qual o estado final vai apontar para possibitar nr de ocorrencias maior que numMin

				//transicao do estado final para o estado inicial do clone
				this.getFinalState().addTransition(new Transition(at.getInitialState()));

				//mudar o estado final para o estado final do clone	
				this.setFinalState(at.getFinalState());

			}
		}
		State newFinalState = new State();

		//transicao do estado final para o novo estado final
		this.getFinalState().addTransition(new Transition(newFinalState));

		//mudar o estado final 
		this.setFinalState(newFinalState);

		//adicionar transicao para possibilitar o loop
		this.getFinalState().addTransition(new Transition(stateTmp));

	}
	public void quantifierMinMax(int numMin, int numMax)//{numMin,numMax}
	{
		Vector <State> statesTmp= new Vector<State>();
		Automata tmp = (Automata)clone(this);
		for(int i = 0; i<numMax;i++)
		{
			if(i==0)
			{
				if(i>=numMin)
					statesTmp.add(this.getInitialState());//guardar os nos que vao apontar para o final

				State newInitialState = new State();

				//adicionar transicao do novo estado inicial para o estado inicial 
				newInitialState.addTransition(new Transition(this.getInitialState()));

				//mudar o estado inicial
				this.setInitialState(newInitialState);
			}
			else
			{
				Automata at = (Automata)clone(tmp);

				if(i>=numMin)
					statesTmp.add(at.getInitialState());//guardar os nos que vao apontar para o final

				//transicao do estado final para o estado inicial do clone
				this.getFinalState().addTransition(new Transition(at.getInitialState()));

				//mudar o estado final para o estado final do clone	
				this.setFinalState(at.getFinalState());

			}
		}
		State newFinalState = new State();

		//transicao do estado final para o novo estado final
		this.getFinalState().addTransition(new Transition(newFinalState));

		//mudar o estado final 
		this.setFinalState(newFinalState);

		//adicionar transicao possibilitar o nr minimo
		for(int i =0;i< statesTmp.size();i++)
			statesTmp.elementAt(i).addTransition(new Transition(this.getFinalState()));

	}


	

}
